#include <stdbool.h>
#include <stdio.h>
#include <openssl/evp.h>
#include <openssl/aes.h>

// constant 128 bit iv for encryption because we will use only one password
const unsigned char *iv = (unsigned char *)"j6dutap7vkht4n8d";

/*
 * Takes as arguments the size of the file buf that will
 * be in memory at a time and multiplies it by mulChunkSize to get
 * the whole chunk size
 * This is in order to be more efficient because (int) division is
 * imprecise and slower than multiplication
 * Returns after every finished chunk
 *
 * Returns:
 * 0 if inStr ended,
 * the index of where to start reading the file in the next run of the function
 * -1 if failed opening files
 */
long
splitFile(long startIndex, const char *inStr, const char *outStr,
		long mulChunkSize, unsigned int memChunkSize, const char *key)
{
	FILE *in_fil, *out_fil;
	unsigned char buf[memChunkSize], buf_enc[memChunkSize + AES_BLOCK_SIZE];
	int act_read, buf_enc_len;
	bool reachedEOF = false;

	EVP_CIPHER_CTX *ctx;
	ctx = EVP_CIPHER_CTX_new();
	EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, (unsigned char *)key,
			iv);

	in_fil = fopen(inStr, "r");
	out_fil = fopen(outStr, "w");

	if (!in_fil || !out_fil)
		return -1;

	fseek(in_fil, startIndex, SEEK_SET); // go to index
	for (long i = 0; i < mulChunkSize; ++i) {
		act_read = fread(buf, 1, memChunkSize, in_fil);

		// encrypt
		EVP_EncryptUpdate(ctx, buf_enc, &buf_enc_len, buf, act_read);
		fwrite(buf_enc, 1, buf_enc_len, out_fil);

		if (act_read < memChunkSize) { // reached end of file
			reachedEOF = true;
			break;
		}
	}
	if (!reachedEOF)
		act_read = fread(buf, 1, 1, in_fil);

	EVP_EncryptFinal_ex(ctx, buf_enc, &buf_enc_len);
	fwrite(buf_enc, 1, buf_enc_len, out_fil);

	fclose(in_fil);
	fclose(out_fil);
	EVP_CIPHER_CTX_free(ctx);

	if (!reachedEOF && act_read) {
		return mulChunkSize*memChunkSize+startIndex;
	}
	return 0;
}

/*
 * Opens inStr, decrypts its content and appends it to outStr
 *
 * Returns:
 * 0 if successful,
 * -1 if failed opening files
 */
char
concatFiles(const char *inStr, const char *outStr, unsigned int memChunkSize,
		const char *key)
{
	FILE *in_fil, *out_fil;

	// stores data from read file
	unsigned char buf[memChunkSize], buf_dec[memChunkSize + AES_BLOCK_SIZE];
	int act_read, buf_dec_len;

	EVP_CIPHER_CTX *ctx;
	ctx = EVP_CIPHER_CTX_new();
	EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, (unsigned char *)key,
			iv);

	in_fil = fopen(inStr, "r");
	out_fil = fopen(outStr, "a");

	if (!in_fil || !out_fil)
		return -1;

	do {
		act_read = fread(buf, 1, memChunkSize, in_fil);
		if (!act_read)
			break;

		// decrypt
		EVP_DecryptUpdate(ctx, buf_dec, &buf_dec_len, buf, act_read);
		fwrite(buf_dec, 1, buf_dec_len, out_fil);
	} while (act_read == memChunkSize); // read file not finished

	EVP_DecryptFinal_ex(ctx, buf_dec, &buf_dec_len);
	fwrite(buf_dec, 1, buf_dec_len, out_fil);

	fclose(in_fil);
	fclose(out_fil);
	EVP_CIPHER_CTX_free(ctx);
	return 0;
}
